﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using DimensionsHelper.Common.Contracts.Excel;
using DimensionsHelper.Common.Contracts.Excel.Options;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.Services.Excel;

namespace DimensionsHelper.Services.DataTable;

public class SampleDataTable : IEnumerable<SampleDataRow>
{
    private readonly Dictionary<string, SampleDataColumn> _columnMap = new();
    private readonly List<SampleDataColumn> _columns = [];
    private readonly List<SampleDataRow> _rows = [];
    private readonly Dictionary<object, double> _weightingMap = new();
    private SampleDataColumn? _keyColumn;

    /// <summary>
    ///     是否自动添加行头。
    /// </summary>
    public bool AutoGenerateRowHeader { get; set; } = false;

    /// <summary>
    ///     是否加权。
    /// </summary>
    public bool ApplyWeight { get; set; } = false;

    /// <summary>
    ///     是否重复计算样本。
    /// </summary>
    public bool Reduplicate { get; set; } = false;

    /// <summary>
    ///     数据表中的数值保留小数位数。
    /// </summary>
    public uint Decimals { get; set; }

    /// <summary>
    ///     使用数字索引获取数据表的行，如果此索引的行不存在，将创建一个。
    /// </summary>
    /// <param name="index">行索引</param>
    /// <returns>指定索引的数据行</returns>
    public SampleDataRow this[int index]
    {
        get
        {
            EnsureRowCapacity(index + 1);
            return _rows[index];
        }
    }

    /// <summary>
    ///     使用数据行头文本查找数据行，如果有多个相同的，将返回第一个；如果不存在，将创建一个并添加到末尾。
    /// </summary>
    /// <param name="rowHeader">数据行头文本</param>
    /// <returns>数据行对象</returns>
    public SampleDataRow this[string rowHeader] => GetOrAdd(rowHeader);

    /// <summary>
    ///     当前表格的最后一行。
    /// </summary>
    public SampleDataRow LastRow
    {
        get
        {
            EnsureRowCapacity(1);
            return _rows[^1];
        }
    }

    /// <summary>
    ///     当前数据表的行数
    /// </summary>
    public int Rows => _rows.Count;

    /// <summary>
    ///     当前数据表的列数
    /// </summary>
    public int Columns => _columns.Count;

    public IEnumerator<SampleDataRow> GetEnumerator()
    {
        return _rows.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }


    /// <summary>
    ///     尝试获取加权权值，如果指定键存在，返回true，否则，返回false。
    /// </summary>
    /// <param name="key">指定键名</param>
    /// <param name="factor">查找到的权值，如果键不存在，此值是null。</param>
    /// <returns>如果键存在，返回true，否则，返回false。</returns>
    public bool TryGetWeightingFactor(object key, out double factor)
    {
        return _weightingMap.TryGetValue(key, out factor);
    }

    private void EnsureRowCapacity(int capacity)
    {
        while (_rows.Count < capacity)
        {
            var header = string.Empty;

            if (AutoGenerateRowHeader)
            {
                header = "R" + (_rows.Count + 1);
            }

            var row = new SampleDataRow(this, _rows.Count, header);
            _rows.Add(row);
        }
    }

    internal void EnsureColumnCapacityInternal(int capacity)
    {
        while (_columns.Count < capacity)
        {
            var col = new SampleDataColumn(this,
                StringHelper.BuildName("Column", n => _columnMap.ContainsKey(n.ToLower())), _columns.Count);
            _columnMap.Add(col.ColumnName, col);
            _columns.Add(col);
        }
    }

    private SampleDataRow GetOrAdd(string rowHeader)
    {
        var row = FindRow(rowHeader);

        if (row != null)
        {
            return row;
        }

        row = new SampleDataRow(this, Rows, rowHeader);
        _rows.Add(row);

        return row;
    }


    /// <summary>
    ///     添加多个列，按列表顺序依次添加。
    /// </summary>
    /// <param name="headers">行头列表</param>
    public void AppendRows(IEnumerable<string> headers)
    {
        foreach (var header in headers)
        {
            var row = new SampleDataRow(this, Rows, header);
            _rows.Add(row);
        }
    }

    /// <summary>
    ///     查找指定行头文本的数据行，如果存在多个相同的，将返回第一个。
    /// </summary>
    /// <param name="rowHeader">数据行头文本</param>
    /// <returns>查找到的第一个数据行，如果没有找到符合的，将返回null</returns>
    public SampleDataRow? FindRow(string rowHeader)
    {
        return _rows.Count == 0 ? null : _rows.FirstOrDefault(r => r.Header.Equals(rowHeader));
    }

    /// <summary>
    ///     对行名进行升序排列
    /// </summary>
    public void SortRow()
    {
        _rows.Sort((a, b) =>
        {
            if (a.IsFixedPosition || b.IsFixedPosition)
            {
                return 0;
            }

            if (double.TryParse(a.Header, out var dA) && double.TryParse(b.Header, out var dB))
            {
                var res = dA - dB;
                return res == 0 ? 0 : res > 0 ? 1 : -1;
            }

            return Comparer<string>.Default.Compare(a.Header, b.Header);
        });
    }

    private void ComputeRowsAhead(int startRow, int columnIndex, out int rowsCount, out double samples,
        out double subtotal)
    {
        rowsCount = 0;
        samples = 0;
        subtotal = 0;

        while (startRow >= 0)
        {
            var row = _rows[startRow];

            if (row.NoSample || row.Kind == DataRowKind.Base)
            {
                break;
            }

            rowsCount++;
            samples += row[columnIndex].SampleCount;
            subtotal += row[columnIndex].Value;
            startRow--;
        }
    }

    private void AppendSubtotalOrAverage(string rowHeader, DataRowKind kind)
    {
        Debug.Assert(
            kind == DataRowKind.Subtotal || kind == DataRowKind.Average || kind == DataRowKind.SampleAverage,
            $"只支持行类型'{nameof(DataRowKind.Subtotal)}'或'{nameof(DataRowKind.Average)}'或'{nameof(DataRowKind.SampleAverage)}'");

        var newRow = new SampleDataRow(this, Rows, rowHeader)
        {
            Kind = kind
        };

        foreach (var col in _columns.Where(col => !col.IsKey))
        {
            ComputeRowsAhead(Rows - 1, col.ColumnIndex, out var rowsCount, out var samples, out var subtotal);
            newRow[col.ColumnIndex].Value = kind switch
            {
                DataRowKind.Subtotal => subtotal,
                DataRowKind.SampleAverage => subtotal / samples,
                _ => subtotal / rowsCount,
            };
        }

        _rows.Add(newRow);
    }

    /// <summary>
    ///     向当前数据表开头添加基数行。
    /// </summary>
    public void PrependBase()
    {
        foreach (var t in _rows)
        {
            t.RowIndex++;
        }

        var row = new SampleDataRow(this, 0, "基数") { Kind = DataRowKind.Base };
        row.EnsureCapacity(Columns);
        _rows.Insert(0, row);
    }

    /// <summary>
    ///     向当前数据表末尾追加基数行。
    /// </summary>
    public void AppendBase()
    {
        var row = new SampleDataRow(this, 0, "基数") { Kind = DataRowKind.Base };
        row.EnsureCapacity(Columns);
        _rows.Add(row);
    }

    /// <summary>
    ///     向当前数据表末尾追加小计行，并计算总和，遇到任意一个<see cref="SampleDataRow.NoSample" />为true的行时，将结束计数。
    /// </summary>
    public void AppendSubtotal()
    {
        AppendSubtotalOrAverage("小计", DataRowKind.Subtotal);
    }

    /// <summary>
    ///     向当前数据表末尾追加行均值行，并计算行均值，遇到任意一个<see cref="SampleDataRow.NoSample" />为true的行时，将结束计数。
    /// </summary>
    public void AppendAverage()
    {
        AppendSubtotalOrAverage("行均值", DataRowKind.Average);
    }

    /// <summary>
    ///     向当前数据表末尾追加样本均值行，并计算样本均值，遇到任意一个<see cref="SampleDataRow.NoSample" />为true的行时，将结束计数。
    /// </summary>
    public void AppendSampleAverage()
    {
        AppendSubtotalOrAverage("样本均值", DataRowKind.SampleAverage);
    }

    /// <summary>
    ///     根据列名获取数据列，如果列不存在，将在末尾创建一个。
    /// </summary>
    /// <param name="columnName">指定列名，不区分大小写</param>
    /// <returns>指定列名的数据列</returns>
    public SampleDataColumn GetColumn(string columnName)
    {
        if (_columnMap.TryGetValue(columnName.ToLower(), out var col))
        {
            return col;
        }

        col = new SampleDataColumn(this, columnName, _columns.Count);
        _columnMap.Add(columnName.ToLower(), col);
        _columns.Add(col);

        return col;
    }

    /// <summary>
    ///     根据数据列索引获取列对象，如果列不存在，将创建新的。
    /// </summary>
    /// <param name="index">指定列索引</param>
    /// <returns>指定列名的数据列</returns>
    public SampleDataColumn GetColumn(int index)
    {
        EnsureColumnCapacityInternal(index + 1);
        return _columns[index];
    }

    /// <summary>
    ///     移除指定索引的行
    /// </summary>
    /// <param name="index">指定行索引</param>
    public void RemoveRowAt(int index)
    {
        if (index < 0 || index >= _rows.Count)
        {
            return;
        }

        _rows.RemoveAt(index);

        for (var i = index; i < _rows.Count; i++)
        {
            _rows[i].RowIndex--;
        }
    }

    /// <summary>
    ///     移除指定索引的列
    /// </summary>
    /// <param name="index">指定列索引</param>
    public void RemoveColumnAt(int index)
    {
        if (index < 0 || index >= _columns.Count)
        {
            return;
        }

        _columnMap.Remove(_columns[index].ColumnName);
        _columns.RemoveAt(index);

        foreach (var row in _rows)
        {
            row.RemoveAt(index);
        }

        for (var i = index; i < _columns.Count; i++)
        {
            _columns[index].ColumnIndex--;
        }
    }

    /// <summary>
    ///     移除指定名称的数据列
    /// </summary>
    /// <param name="columnName">指定数据列名，不区分大小写</param>
    public void RemoveColumn(string columnName)
    {
        if (!_columnMap.TryGetValue(columnName.ToLower(), out var col))
        {
            return;
        }

        RemoveColumnAt(col.ColumnIndex);
    }

    /// <summary>
    ///     设置指定列为样本ID列，此列数据应包含样本的唯一标识符。
    /// </summary>
    /// <param name="columnName">列名，不区分大小写</param>
    public void SetKeyColumn(string columnName)
    {
        if (!_columnMap.TryGetValue(columnName.ToLower(), out var col))
        {
            return;
        }

        if (_keyColumn != null)
        {
            _keyColumn.IsKey = false;
        }

        _keyColumn = col;
        col.IsKey = true;
    }


    public void SetSampleWeightingFactor(object id, double factor)
    {
        if (_weightingMap.TryGetValue(id, out _))
        {
            _weightingMap[id] = factor;
        }
        else
        {
            _weightingMap.Add(id, factor);
        }
    }

    /// <summary>
    ///     将数据转换成二维数组，二维数组第一维是行，第二维是列，如果单元格值可以转换成整数，
    ///     将在数组中赋值整数。
    /// </summary>
    /// <returns></returns>
    public object[,] ToArray()
    {
        var arr = new object[Rows, Columns];

        for (var r = 0; r < Rows; r++)
        {
            for (var c = 0; c < Columns; c++)
            {
                var val = this[r][c].Value;

                if (val % 1 <= double.Epsilon * 100)
                {
                    arr[r, c] = Convert.ToInt32(val);
                }
                else
                {
                    arr[r, c] = val;
                }
            }
        }

        return arr;
    }

    /// <summary>
    ///     清空当前数据表中的所有行和列。
    /// </summary>
    public void Clear()
    {
        _rows.Clear();
        _columns.Clear();
        _columnMap.Clear();
        _weightingMap.Clear();
    }

    /// <summary>
    ///     将当前表格中的内容输出到已有的Excel中。
    /// </summary>
    /// <param name="worksheet">已有的Excel工作表</param>
    /// <param name="startRow">输出的起始行，如果表格间需要空行，应当提前考虑。</param>
    /// <param name="decimals">输出小数时，保留的小数位数。</param>
    public void WriteToExcel(IExcelWorksheetOperator worksheet, int startRow, int decimals)
    {
        if (_rows.Count == 0 || _columns.Count == 0)
        {
            return;
        }

        var row = startRow;

        // 列头
        for (var i = 0; i < _columns.Count; i++)
        {
            worksheet.GetRange(row, i + 2).Value = _columns[i].ColumnName;
        }

        // 行
        foreach (var r in _rows)
        {
            row++;

            worksheet.GetRange(row, 1).Value = int.TryParse(r.Header, out var iHeader)
                ? iHeader 
                : r.Header;

            for (var i = 0; i < _columns.Count; i++)
            {
                worksheet.GetRange(row, i + 2).Value = r[i].Value % 1 == 0
                    ? Convert.ToInt32(r[i].Value)
                    : Math.Round(r[i].Value, decimals);
            }

            if (r.NoSample || r.Kind == DataRowKind.Base)
            {
                var rowRange = worksheet.GetRange(row, 1, row, _columns.Count + 1);
                rowRange.SetStyle(new RangeStyleOptions
                {
                    Font = new FontStyleOptions
                    {
                        FontType = FontType.Bold
                    },
                    InteriorColor = ExcelStyleColors.LightGray
                });
            }
        }

        worksheet.GetRange(startRow, 1, row, _columns.Count + 1)
            .SetRangeBorderStyle(BorderPosition.Horizontal | BorderPosition.Vertical | BorderPosition.Around, BorderStyle.Thin);
        worksheet.GetRange(startRow, 1, row, 1).SetStyle(new RangeStyleOptions
            { HAlign = ExcelHAlign.Left, Font = { FontType = FontType.Bold } });
        worksheet.GetRange(startRow, 2, row, _columns.Count + 1).SetStyle(new RangeStyleOptions
            { HAlign = ExcelHAlign.Center });
    }
}

internal enum SampleChangedType
{
    Add,
    Remove
}

public class SampleDataColumn(SampleDataTable table, string columnName, int columnIndex)
{
    private readonly HashSet<object> _baseSamples = [];
    private double _baseValue;
    private bool _isDirty = true;

    /// <summary>
    ///     当前列是否是关键字
    /// </summary>
    public bool IsKey { get; internal set; }

    /// <summary>
    ///     数据列名称
    /// </summary>
    public string ColumnName { get; } = columnName;

    /// <summary>
    ///     数据列索引
    /// </summary>
    public int ColumnIndex { get; internal set; } = columnIndex;

    /// <summary>
    ///     获取当前列的样本基数。
    /// </summary>
    public double BaseValue
    {
        get
        {
            if (!_isDirty)
            {
                return _baseValue;
            }

            if (table.ApplyWeight)
            {
                double val = 0;

                foreach (var sample in _baseSamples)
                {
                    if (table.TryGetWeightingFactor(sample, out var factor))
                    {
                        val += factor;
                    }
                    else
                    {
                        val++;
                    }
                }

                _isDirty = false;
                _baseValue = val;
            }
            else
            {
                _baseValue = _baseSamples.Count;
            }

            return _baseValue;
        }
    }

    internal void OnSampleChangedInternal(Sample changedSample, SampleChangedType changedType)
    {
        if (changedType == SampleChangedType.Add)
        {
            if (_baseSamples.Add(changedSample.Id))
            {
                _isDirty = true;
            }
        }
        else
        {
            if (_baseSamples.Remove(changedSample.Id))
            {
                _isDirty = true;
            }
        }
    }
}